package org.jflame.commons.convert;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.NumberFormat;
import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.commons.lang3.tuple.ImmutablePair;

public class NumberToStringConverter extends ObjectToStringConverter<Number> implements ConvertibleType {

    private static final double MIN_FOR_SCIN = Math.pow(10, 7);
    private static final double MAX_FOR_SCIN = Math.pow(10, -3);

    private NumberFormat format = null;
    /**
     * 是否使用科学计数法表示,默认的double类型大于等于10<sup>7</sup>或小于-10<sup>-3</sup>时字符串表示会使用科学计数法
     */
    private boolean useSCINotation = false;

    private NumberFormat formatForSCIN = null;

    public NumberToStringConverter() {
        formatForSCIN = NumberFormat.getInstance();
        formatForSCIN.setGroupingUsed(false);
        formatForSCIN.setMaximumFractionDigits(10);
    }

    public NumberToStringConverter(NumberFormat format) {
        this.format = format;
    }

    @Override
    public String convert(Number source) {
        if (source == null) {
            return null;
        }
        if (format != null) {
            try {
                return format.format(source);
            } catch (IllegalArgumentException e) {
                throw new ConvertException(e);
            }
        } else {
            if (source instanceof BigDecimal) {
                BigDecimal b = (BigDecimal) source;
                // stripTrailingZeros大数时会变成科学计数法
                if (b.scale() > 0) {
                    return b.stripTrailingZeros()
                            .toPlainString();
                } else {
                    return b.toPlainString();
                }
            } else if (!useSCINotation && ((source instanceof Long) || (source instanceof Double))) {
                // 避免使用科学计数法
                double d = source.doubleValue();
                if (d < MIN_FOR_SCIN || d >= MAX_FOR_SCIN) {
                    return formatForSCIN.format(source);
                }
            }
            return String.valueOf(source);
        }
    }

    public boolean isUseSCINotation() {
        return useSCINotation;
    }

    public void setUseSCINotation(boolean useSCINotation) {
        this.useSCINotation = useSCINotation;
    }

    @Override
    public Set<ImmutablePair<Class<?>,Class<?>>> getConvertibleTypes() {
        Set<ImmutablePair<Class<?>,Class<?>>> convertibleTypes = new LinkedHashSet<>();
        convertibleTypes.add(ImmutablePair.of(Integer.class, String.class));
        convertibleTypes.add(ImmutablePair.of(Long.class, String.class));
        convertibleTypes.add(ImmutablePair.of(Double.class, String.class));
        convertibleTypes.add(ImmutablePair.of(Byte.class, String.class));
        convertibleTypes.add(ImmutablePair.of(Short.class, String.class));
        convertibleTypes.add(ImmutablePair.of(Float.class, String.class));
        convertibleTypes.add(ImmutablePair.of(BigDecimal.class, String.class));
        convertibleTypes.add(ImmutablePair.of(BigInteger.class, String.class));
        return convertibleTypes;
    }
}
